package com.srymy.gb28181.part1.sip;

import com.srymy.gb28181.part1.util.SsrcUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;

import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.TooManyListenersException;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class SipServerLayer implements SipListener {
    @Value("${gb28181.deviceId:34020000002000000001}")
    String deviceId;
    @Value("${gb28181.ip:127.0.0.1}")
    String ip;
    @Value("${gb28181.port:5060}")
    Integer port;

    @Value("${media.rtp.ip:127.0.0.1}")
    String mediaRtpIp;
    @Value("${media.rtp.port:9000}")
    Integer mediaRtpPort;

    static final String CATALOG_FORMAT = "<?xml version=\"1.0\"?>\n<Query>\n<CmdType>Catalog</CmdType>\n<SN>%s</SN>\n<DeviceID>%s</DeviceID>\n</Query>\n";
    static final String GB28181_INVITE_FORMAT = "v=0\n" +
            "o=%s 0 0 IN IP4 %s\n" +
            "s=%s\n" +
            "c=IN IP4 %s\n" +
            "t=0 0\n" +
            "m=video %s RTP/AVP 96 97 98\n" +
            "a=rtpmap:96 PS/90000\n" +
            "a=rtpmap:97 MPEG4/90000\n" +
            "a=rtpmap:98 H264/90000\n" +
            "a=recvonly\n" +
            "y=%s\n";
    private AtomicInteger SN = new AtomicInteger(20000);
    public SipSrymyMessageProcessor getMessageProcessor() {
        return messageProcessor;
    }

    public void setMessageProcessor(SipSrymyMessageProcessor messageProcessor) {
        this.messageProcessor = messageProcessor;
    }

    private SipSrymyMessageProcessor messageProcessor;

    private SipStack sipStack;

    private SipFactory sipFactory;

    private AddressFactory addressFactory;

    private HeaderFactory headerFactory;

    private MessageFactory messageFactory;

    private SipProvider sipProvider;

    private ListeningPoint tcp;

    private ListeningPoint udp;

    /**
     * Here we initialize the SIP stack.
     */
    @SuppressWarnings("deprecation")
    public SipServerLayer(String ip, int port) throws PeerUnavailableException,
            TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TooManyListenersException {
        sipFactory = SipFactory.getInstance();
        sipFactory.setPathName("gov.nist");
        Properties properties = new Properties();
        properties.setProperty("javax.sip.STACK_NAME", "srymy-GB28181");
        properties.setProperty("javax.sip.IP_ADDRESS", ip);

        /**
         * sip_server_log.log 和 sip_debug_log.log
         * 	public static final int TRACE_NONE = 0;
         public static final int TRACE_MESSAGES = 16;
         public static final int TRACE_EXCEPTION = 17;
         public static final int TRACE_DEBUG = 32;
         */
        properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "0");
        properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sip_server_log");
        properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sip_debug_log");

        sipStack = sipFactory.createSipStack(properties);
        headerFactory = sipFactory.createHeaderFactory();
        addressFactory = sipFactory.createAddressFactory();
        messageFactory = sipFactory.createMessageFactory();

        tcp = sipStack.createListeningPoint(ip,  port, "tcp");
        udp = sipStack.createListeningPoint(ip, port, "udp");
        createListeningPoint(ip,port);
    }

    /**
     * 停止监听
     * @throws ObjectInUseException
     */
    public void deleteListeningPoint() throws ObjectInUseException {
        if (tcp != null) {
            sipStack.deleteListeningPoint(tcp);
            sipStack.deleteListeningPoint(udp);
        }
    }

    /**
     * This method uses the SIP stack to send a message. 第一个参数：用户名 第二个参数：IP地址 第三个参数：设备ID
     */
    public synchronized void sendInvite(String toIp,Integer toPort, String deviceId)
            throws ParseException, InvalidArgumentException, SipException {
        log.info("发送Invite ");
        Request request = createRequest(deviceId, toIp+":"+toPort, Request.INVITE, sipProvider.getNewCallId().getCallId(),
                "InviteFromTag", "InviteBranch", Request.INVITE, 20L);
        Address contactAddress = addressFactory.createAddress(addressFactory.createSipURI(deviceId, toIp + ":" + toPort));
        ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);
        request.addHeader(contactHeader);
        SubjectHeader subjectHeader = headerFactory.createSubjectHeader(deviceId+":0,"+deviceId+":0");
        request.addHeader(subjectHeader);
        ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("Application", "SDP");
        String inviteContent = String.format(GB28181_INVITE_FORMAT,deviceId, mediaRtpIp, "Play", mediaRtpIp, mediaRtpPort, SsrcUtil.getRandomString(5));
        request.setContent(inviteContent, contentTypeHeader);
        sipProvider.sendRequest(request);
    }

    /**
     * 创建SIP端口监听，同时监听UDP和TCP
     * @param ip
     * @param port
     * @throws InvalidArgumentException
     * @throws TransportNotSupportedException
     * @throws ObjectInUseException
     * @throws TooManyListenersException
     */
    public void createListeningPoint(String ip, int port) throws InvalidArgumentException, TransportNotSupportedException, ObjectInUseException, TooManyListenersException {
        tcp = sipStack.createListeningPoint(ip,  port, "tcp");
        udp = sipStack.createListeningPoint(ip, port, "udp");
        sipProvider = sipStack.createSipProvider(tcp);
        sipProvider.addSipListener(this);
        sipProvider = sipStack.createSipProvider(udp);
        sipProvider.addSipListener(this);
    }

    private Request createRequest(String deviceId, String toIpAddPort, String method, String callId,
                                  String tag, String branch) throws ParseException, InvalidArgumentException {
        return createRequest(deviceId, toIpAddPort, method, callId, tag, branch, method, 2L);
    }

    private Request createRequest(String deviceId, String toIpAddPort, String method, String callId,
                                  String tag, String branch, String CSeqMethod, Long CSeq)
            throws ParseException, InvalidArgumentException {
        return createRequest(deviceId, toIpAddPort, method, callId, tag, branch, CSeqMethod, CSeq,
                null);
    }

    private Request createRequest(String deviceId, String toIpAddPort, String method, String callId,
                                  String tag, String branch, String CSeqMethod, Long CSeq, String toTag)
            throws ParseException, InvalidArgumentException {
        SipURI from = addressFactory
                .createSipURI(deviceId, ip + ":" + port);
        Address fromNameAddress = addressFactory.createAddress(from);
        FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, tag);

        SipURI toAddress = addressFactory.createSipURI(deviceId, toIpAddPort);
        Address toNameAddress = addressFactory.createAddress(toAddress);
        ToHeader toHeader = headerFactory.createToHeader(toNameAddress, toTag);

        SipURI requestURI = addressFactory.createSipURI(deviceId, toIpAddPort);

        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
        ViaHeader viaHeader = headerFactory
                .createViaHeader(ip, port, "udp", branch);
        viaHeaders.add(viaHeader);

        CallIdHeader callIdHeader = StringUtils.isEmpty(callId) ? sipProvider.getNewCallId()
                : headerFactory.createCallIdHeader(callId);

        CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(CSeq == null ? 1L : CSeq, CSeqMethod);

        MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);
        return messageFactory.createRequest(requestURI, method, callIdHeader, cSeqHeader,
                fromHeader, toHeader, viaHeaders, maxForwards);
    }
    /**
     * This method is called by the SIP stack when a response arrives.
     */
    @Override
    public void processResponse(ResponseEvent responseEvent) {
        try {
            messageProcessor.processResponse(responseEvent, addressFactory, messageFactory, headerFactory, sipProvider);
        } catch (InvalidArgumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * SIP服务端接收消息的方法
     * Content 里面是GBK编码
     * This method is called by the SIP stack when a new request arrives.
     */

    @Override
    public void processRequest(RequestEvent requestEvent) {
        messageProcessor.processRequest(requestEvent, addressFactory, messageFactory, headerFactory, sipProvider);
    }


    /**
     * This method is called by the SIP stack when there's no answer to a
     * message. Note that this is treated differently from an error message.
     */
    @Override
    public void processTimeout(TimeoutEvent evt) {
        messageProcessor.processError("Previous message not sent: " + "timeout");
    }

    /**
     * This method is called by the SIP stack when there's an asynchronous
     * message transmission error.
     */
    @Override
    public void processIOException(IOExceptionEvent evt) {
        messageProcessor.processError("Previous message not sent: " + "I/O Exception");
    }

    /**
     * This method is called by the SIP stack when a dialog (session) ends.
     */
    @Override
    public void processDialogTerminated(DialogTerminatedEvent evt) {
        log.info("会话结束：CallId: {}, DialogId: {}",evt.getDialog().getCallId(), evt.getDialog().getDialogId());
    }

    /**
     * This method is called by the SIP stack when a transaction ends.
     */
    @Override
    public void processTransactionTerminated(TransactionTerminatedEvent evt) {
        log.info("Transaction结束,evt: {}",evt);
    }
}
